package edu.msu.cse.meldru20.project1;

import java.io.Serializable;
import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class EditingView extends View {
	
	private Paint lineColor = new Paint();
	
	// If true then we can edit drawing 
	private boolean isEditable = true;
	
    /**
     * Image drawing scale
     */
    private float imageScale = 1;

    /**
     * Image left margin in pixels
     */
    private float marginLeft = 0;
    
    /**
     * Image top margin in pixels
     */
    private float marginTop = 0;
	
	/**
     * First touch status
     */
    private Touch touch1 = new Touch();
    
    /**
     * Second touch status
     */
    private Touch touch2 = new Touch();
    
    /*
     * The Drawing
     */
    private Drawing theDrawing = new Drawing();
    
    // The paint to draw the stroke with
    private Paint strokepaint = new Paint();
    
    /*
     *  The Drawing Class
     */
    public static class Drawing implements Serializable {
    	static final long serialVersionUID = 10275539472837495L;   	
    	public ArrayList<Stroke> strokes = new ArrayList<Stroke>();
    	public boolean editable = true;
    	public float scale = 1;
    }
    
    /*
     *  The Stroke Class
     */ 
    private static class Stroke implements Serializable {
    	static final long serialVersionUID = 10275539472837494L; 
    	public Point startPoint = new Point();
    	public Point endPoint = new Point();
    	public float strokeWidth = 1;
    	public int strokeColor = 1;
    	
    }   
    
    /*
     *  The Point class
     */   
    private static class Point implements Serializable {
    	static final long serialVersionUID = 10275539472837493L; 
    	public float x=0;
    	public float y=0;
    }
    
	/**
     * Local class to handle the touch status for one touch.
     * We will have one object of this type for each of the 
     * two possible touches.
     */
    private class Touch {
        /**
         * Touch id
         */
        public int id = -1;
        
        /**
         * Current x location
         */
        public float x = 0;
        
        /**
         * Current y location
         */
        public float y = 0;
        
        /**
         * Previous x location
         */
        public float lastX = 0;
        
        /**
         * Previous y location
         */
        public float lastY = 0;
        
        /**
         * Change in x value from previous
         */
        public float dX = 0;
        
        /**
         * Change in y value from previous
         */
        public float dY = 0;
        
        /**
         * Copy the current values to the previous values
         */
        public void copyToLast() {
            lastX = x;
            lastY = y;
        }
        
        /**
         * Compute the values of dX and dY
         */
        public void computeDeltas() {
            dX = x - lastX;
            dY = y - lastY;
        }
    }
	
	public void SetPaintColor(int col)
	{
		lineColor.setColor(col);
	}
	
	public void SetLineWidth(float wid)
	{
		lineColor.setStrokeWidth(wid);
	}
	
	public void SetEditable(boolean edit)
	{
		isEditable = edit;
		theDrawing.editable = edit;
		invalidate();
	}
	
	/**
     * Handle a draw event
     * @param canvas canvas to draw on.
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        canvas.scale(theDrawing.scale, theDrawing.scale);
        
        // Draw the lines based on touches
    	for(Stroke s : theDrawing.strokes)
    	{
    		strokepaint.setColor(s.strokeColor);
    		strokepaint.setStrokeWidth(s.strokeWidth);
    		canvas.drawLine(s.startPoint.x, s.startPoint.y, s.endPoint.x, s.endPoint.y, strokepaint);
    	}
      
    }
    
    /**
     * Handle a touch event
     * @param event The touch event
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {

    	// Handles updating the draw when touch events occur
    	int id = event.getPointerId(event.getActionIndex());
        
        switch(event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
        	touch1.id = id;
            touch2.id = -1;
            getPositions(event);
            touch1.copyToLast();
            return true;
            
        case MotionEvent.ACTION_POINTER_DOWN:
            if(touch1.id >= 0 && touch2.id < 0) {
                touch2.id = id;
                getPositions(event);
                touch2.copyToLast();
                return true;
            }
            break;
            
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            touch1.id = -1;
            touch2.id = -1;
            invalidate();
            return true;
            
        case MotionEvent.ACTION_POINTER_UP:
            if(id == touch2.id) {
                touch2.id = -1;
            } else if(id == touch1.id) {
                // Make what was touch2 now be touch1 by 
                // swapping the objects.
                Touch t = touch1;
                touch1 = touch2;
                touch2 = t;
                touch2.id = -1;
            }
            invalidate();
            return true;
            
        case MotionEvent.ACTION_MOVE:
        	getPositions(event);
            move();
            return true;
        }
        return super.onTouchEvent(event);
    }
    
    /**
     * Get the positions for the two touches and put them
     * into the appropriate touch objects.
     * @param event the motion event
     */
    private void getPositions(MotionEvent event) {

    	// Get the positions of the touch
    	for(int i=0;  i<event.getPointerCount();  i++) {
            
            // Get the pointer id
            int id = event.getPointerId(i);
            
         	// Convert to image coordinates
            float x = (event.getX(i) - marginLeft) / imageScale;
            float y = (event.getY(i) - marginTop) / imageScale;
            
            if(id == touch1.id) {
                touch1.copyToLast();
                touch1.x = x;
                touch1.y = y;
            } else if(id == touch2.id) {
                touch2.copyToLast();
                touch2.x = x;
                touch2.y = y;
            }
        }
        
        invalidate();
        
    }
    
    /**
     * Rotate the image around the point x1, y1
     * @param dAngle Angle to rotate in degrees
     * @param x1 rotation point x
     * @param y1 rotation point y
     */
    public void rotate(float dAngle, float x1, float y1) {
    	
    	for(Stroke s : theDrawing.strokes)
    	{    		
    		// Compute the radians angle
            double rAngle = Math.toRadians(dAngle);
            float ca = (float) Math.cos(rAngle);
            float sa = (float) Math.sin(rAngle);
            
            float xp = 0;
            float yp = 0;
            
            xp = (s.startPoint.x - x1) * ca - (s.startPoint.y - y1) * sa + x1;
            yp = (s.startPoint.x - x1) * sa + (s.startPoint.y - y1) * ca + y1;
            s.startPoint.x = xp;
            s.startPoint.y = yp;
            
            xp = (s.endPoint.x - x1) * ca - (s.endPoint.y - y1) * sa + x1;
            yp = (s.endPoint.x - x1) * sa + (s.endPoint.y - y1) * ca + y1;
            s.endPoint.x = xp;
            s.endPoint.y = yp;
    	}
    }
    
    /**
     * Determine the angle for two touches
     * @param x1 Touch 1 x
     * @param y1 Touch 1 y
     * @param x2 Touch 2 x
     * @param y2 Touch 2 y
     * @return computed angle in degrees
     */
    private float angle(float x1, float y1, float x2, float y2) {
        float dx = x2 - x1;
        float dy = y2 - y1;
        return (float) Math.toDegrees(Math.atan2(dy, dx));
    }
    
    private void move() {
  
    	// Handle updating of positions of the touch
    	// If no touch1, we have nothing to do
        // This should not happen, but it never hurts
        // to check.
        if(touch1.id < 0) { 
            return;
        }
        
        if(touch1.id >= 0) {
            // At least one touch
            // We are moving
            touch1.computeDeltas();
            
            if(!isEditable)
            {
            	// Move the image
            	for(Stroke s : theDrawing.strokes)
            	{
            		s.startPoint.x += touch1.dX;
            		s.startPoint.y += touch1.dY;
            		s.endPoint.x += touch1.dX;
            		s.endPoint.y += touch1.dY;
            	}
            }
        }
        
        if(touch2.id >= 0) {
            // Two touches
            
            /*
             * Rotation
             */
            float angle1 = angle(touch1.lastX, touch1.lastY, touch2.lastX, touch2.lastY);
            float angle2 = angle(touch1.x, touch1.y, touch2.x, touch2.y);
            float da = angle2 - angle1;
            rotate(da, touch1.x, touch1.y);
            
            /*
             * Scaling
             */
            double dist1 = Distance(touch1.lastX,touch2.lastX,touch1.lastY,touch2.lastY);
            double dist2 = Distance(touch1.x,touch2.x,touch1.y,touch2.y);
            double inc = (dist2-dist1)/dist1;
            Scale((float)inc);
        }
        
        if(isEditable)
        {
	        // If ONLY 1 finger
	        if(touch1.id >= 0 && touch2.id < 0)
	        {
	        	// Add a stroke to list of strokes
	            Stroke s = new Stroke();
	            s.startPoint.x = touch1.lastX;
	            s.startPoint.y = touch1.lastY;
	            s.endPoint.x = touch1.x;
	            s.endPoint.y = touch1.y;
	            s.strokeColor = lineColor.getColor();
	            s.strokeWidth = lineColor.getStrokeWidth();
	            theDrawing.strokes.add(s);
	        }
        }
        
        invalidate();
    }
    
    private void Scale(float percentIncrease)
    {
    	theDrawing.scale += theDrawing.scale * percentIncrease;
    	imageScale = theDrawing.scale;
    }
    
    private double Distance(float x1,float x2,float y1,float y2)
    {
    	double distance=0;    	
    	double dx = Math.pow(x2-x1,2);
    	double dy = Math.pow(y2-y1,2);	
    	distance = Math.sqrt(dy+dx);
    	return distance;
    }

	public EditingView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		
	}

	public EditingView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public EditingView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}
	
	/**
     * Save the view state to a bundle
     * @param key key name to use in the bundle
     * @param bundle bundle to save to
     */
    public void putToBundle(String key, Bundle bundle) {
        bundle.putSerializable(key, theDrawing);
    }
    
    public void getFromBundle(String key, Bundle bundle) {
    	
    	theDrawing = (Drawing)bundle.getSerializable(key);
    	
    	// Set the properties
    	Stroke s = theDrawing.strokes.get(theDrawing.strokes.size()-1);
    	SetPaintColor(s.strokeColor);
    	SetLineWidth(s.strokeWidth);
    	isEditable = theDrawing.editable;
    	imageScale = theDrawing.scale;
    }
    
    public Drawing GetDrawing()
    {
    	return theDrawing;
    }
    
    public void SetDrawing(Drawing d)
    {
    	theDrawing = d;
    }
}

